/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
define([
    'jquery',
    'underscore',
    'mageUtils',
    'mage/translate',
    'Magento_Catalog/js/product/storage/ids-storage',
    'Magento_Catalog/js/product/storage/data-storage',
    'Magento_Catalog/js/product/storage/ids-storage-compare'
], function ($, _, utils, $t, IdsStorage, DataStore, IdsStorageCompare) {
    'use strict';

    return (function () {

        var /**
             * {Object} storages - list of storages
             */
            storages = {},

            /**
             * {Object} classes - list of classes
             */
            classes = {},

            /**
             * {Object} prototype - methods that will be added to all storage classes to prototype property.
             */
            prototype = {

                /**
                 * Sets data to storage
                 *
                 * @param {*} data
                 */
                set: function (data) {
                    if (!utils.compare(data, this.data()).equal) {
                        this.data(data);
                    }
                },

                /**
                 * Adds some data to current storage data
                 *
                 * @param {*} data
                 */
                add: function (data) {
                    if (!_.isEmpty(data)) {
                        this.data(_.extend(utils.copy(this.data()), data));
                    }
                },

                /**
                 * Gets current storage data
                 *
                 * @returns {*} data
                 */
                get: function () {
                    return this.data();
                }
            },

            /**
             * Required properties to storage
             */
            storagesInterface =  {
                data: 'function',
                initialize: 'function',
                namespace: 'string'
            },

            /**
             * Private service methods
             */
            _private = {

                /**
                 * Overrides class method and add ability use _super to call parent method
                 *
                 * @param {Object} extensionMethods
                 * @param {Object} originInstance
                 */
                overrideClassMethods: function (extensionMethods, originInstance) {
                    var methodsName = _.keys(extensionMethods),
                        i = 0,
                        length = methodsName.length;

                    for (i; i < length; i++) {
                        if (_.isFunction(originInstance[methodsName[i]])) {
                            originInstance[methodsName[i]] = extensionMethods[methodsName[i]];
                        }
                    }

                    return originInstance;
                },

                /**
                 * Checks is storage implement interface
                 *
                 * @param {Object} classInstance
                 *
                 * @returns {Boolean}
                 */
                isImplementInterface: function (classInstance) {
                    _.each(storagesInterface, function (key, value) {
                        if (typeof classInstance[key] !== value) {
                            return false;
                        }
                    });

                    return true;
                }
            },

            /**
             * Subscribers list
             */
            subsctibers = {};

        (function () {
            /**
             * @param {Object} config
             * @return void
             */
            classes[IdsStorage.name] = function (config) {
                _.extend(this, IdsStorage, config);
            };

            /**
             * @param {Object} config
             * @return void
             */
            classes[IdsStorageCompare.name] = function (config) {
                _.extend(this, IdsStorageCompare, config);
            };

            /**
             * @param {Object} config
             * @return void
             */
            classes[DataStore.name] = function (config) {
                _.extend(this, DataStore, config);
            };

            _.each(classes, function (classItem) {
                classItem.prototype = prototype;
            });
        })();

        return {

            /**
             * Creates new storage or returns if storage with declared namespace exist
             *
             * @param {Object} config - storage config
             * @throws {Error}
             * @returns {Object} storage instance
             */
            createStorage: function (config) {
                var instance,
                    initialized;

                if (storages[config.namespace]) {
                    return storages[config.namespace];
                }

                instance = new classes[config.className](config);

                if (_private.isImplementInterface(instance)) {
                    initialized = storages[config.namespace] = instance.initialize();
                    this.processSubscribers(initialized, config);

                    return initialized;
                }

                throw new Error('Class ' + config.className + $t('does not implement Storage Interface'));
            },

            /**
             * Process subscribers
             *
             * Differentiate subscribers by their namespaces: recently_viewed or recently_compared
             * and process callbacks. Callbacks can be add through onStorageInit function
             *
             * @param {Object} initialized
             * @param {Object} config
             * @return void
             */
            processSubscribers: function (initialized, config) {
                if (subsctibers[config.namespace]) {
                    _.each(subsctibers[config.namespace], function (callback) {
                        callback(initialized);
                    });

                    delete subsctibers[config.namespace];
                }
            },

            /**
             * Listens storage creating by namespace
             *
             * @param {String} namespace
             * @param {Function} callback
             * @return void
             */
            onStorageInit: function (namespace, callback) {
                if (storages[namespace]) {
                    callback(storages[namespace]);
                } else {
                    subsctibers[namespace] ?
                        subsctibers[namespace].push(callback) :
                        subsctibers[namespace] = [callback];
                }
            },

            /**
             * Gets storage by namespace
             *
             * @param {String} namespace
             *
             * @returns {Object} storage insance
             */
            getStorage: function (namespace) {
                return storages[namespace];
            }
        };
    })();
});